"""
支持向量机

SVM是最接近深度学习的机器学习算法
支持向量机的分类方法，是在这组分布中找出一个超平面作为决策边界，使模型在数据上的分类误差尽量接近于小，尤其是在未知数据集上的分类误差（泛化误差）尽量小。

决策边界的边际(margin)
拥有更大边际的决策边界在分类中的泛化误差更小
边际很小的情况，是一种模型在训练集上表现很好，却在测试集上表现糟糕的情况，所以会“过拟合”。所以我们在找寻决策边界的时候，希望边际越大越好。

支持向量机，就是通过找出边际最大的决策边界，来对数据进行分类的分类器。

和逻辑回归中的过程一样，SVM也是通过最小化损失函数来求解一个用于后续模型使用的重要信息：决策边界。

Sklearn中的SVM
svm.LinearSVC                   线性支持向量分类
svm.LinearSVR                   线性支持向量回归
svm.SVC                         多维支持向量分类
svm.SVR                         多维支持向量回归
svm.NuSVC                       Nu多维支持向量分类
svm.NuSVR                       Nu多维支持向量回归
svm.OneClassSVM                 无监督异常值检测

直接使用libsvm函数
svm.libsvm.cross_validation     SVM专用的交叉检验
svm.libsvm.decision_function    SVM专用的预测边际函数
svm.libsvm.fit                  使用libsvm训练模型
svm.libsvm.predict              给定模型预测x的目标值
svm.libsvm.predict_proba        预测概率

除了特别表明是线性的两个类LinearSVC和LinearSVR之外，其他的所有类都是同时支持线性和非线性的。NuSVC和NuSVC可以手动调节支持向量的数目，其他参数都与最常用的SVC和SVR一致。注意OneClassSVM是无监督的类。
Libsvm是台湾大学林智仁(Lin Chih-Jen)教授等人开发设计的一个简单、易于使用和快速有效的英文的SVM库。

"""
from sklearn.datasets import make_blobs
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np

X,y = make_blobs(n_samples=50, centers=2, random_state=0,cluster_std=0.6)
# plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
# plt.xticks([])
# plt.yticks([])
# plt.show()


# # 解释meshgrid、ravel、vstack
# a = np.array([1, 2, 3])
# b = np.array([7, 8])
# v1, v2 = np.meshgrid(a, b)
# """
# meshgrid：Return coordinate matrices from coordinate vectors.
# meshgrid会把N个m*n个向量，变成包含N个向量的数组，每个数组的维度是((m2*n2),(m1*n1),(m3*n3),(m4*n4),...)。
# 前两个的m*n会调换位置，后面的不变。
# """
#
# print(v1)       # [[1 2 3],[1 2 3]]
# print(v1.ravel())   # [1, 2, 3, 1, 2, 3]
# """ravel会把向量拍平了变成一维数组，好像用flatten更合适"""
#
# print(v2)       # [[7 7 7],[8 8 8]]
# print(v2.ravel())   # [7, 7, 7, 8, 8, 8]
# v = np.vstack([v1.ravel(), v2.ravel()]).T


# 首先要有散点图
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap="rainbow")
ax = plt.gca()  # 获取当前的子图，如果不存在，则创建新的子图
xlim = ax.get_xlim()
ylim = ax.get_ylim()  # 默认创建(0.0, 1.0)范围内的横纵坐标
# 要画决策边界，必须要有网格
axisx = np.linspace(xlim[0], xlim[1], 30)
axisy = np.linspace(ylim[0], ylim[1], 30)
axisy, axisx = np.meshgrid(axisy, axisx)        # meshgrid生成x*y个坐标
# 将特征向量转换为特征矩阵的函数
# 核心是将两个特征向量广播，以便获取y.shape * x.shape这么多个坐标点的横坐标和纵坐标

xy = np.vstack([axisx.ravel(), axisy.ravel()]).T
# 获取y.shape * x.shape这么多个坐标点
# 其中ravel()是降维函数，vstack能够将多个结构一致的一维数组按行堆叠起来
# xy就是已经形成的网络，它是遍布在整个画布上的密集的点

clf = SVC(kernel="linear").fit(X, y)            # 建模，通过fit计算出对应的决策边界

P = clf.decision_function(xy).reshape(axisx.shape)
# 重要接口decision_function，返回每个输入的样本所对应的到决策边界的距离
# 然后再将这个距离转换为axisx的结构

# 画决策边界和平行于决策边界的超平面
ax.contour(axisx, axisy, P
           , colors="k"
           , levels=[-1, 0, 1]
           , alpha=0.5
           , linestyles=["--", "-", "--"])
ax.set_xlim(xlim)
ax.set_ylim(ylim)


# 将上述过程包装成函数：
def plot_svc_decision_function(model, ax=None):
    if ax is None:
        ax = plt.gca()
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    x = np.linspace(xlim[0], xlim[1], 30)
    y = np.linspace(ylim[0], ylim[1], 30)
    Y, X = np.meshgrid(y, x)
    xy = np.vstack([X.ravel(), Y.ravel()]).T
    P = model.decision_function(xy).reshape(X.shape)

    ax.contour(X, Y, P, colors="k", levels=[-1, 0, 1], alpha=0.5, linestyles=["--", "-", "--"])
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

#则可以写作：
clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
plt.show()

from sklearn.datasets import make_circles
X,y = make_circles(100, factor=0.1, noise=.1)
# X.shape
# y.shape
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plt.show()

clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
plt.show()

#定义一个由x计算出来的新维度r
r = np.exp(-(X**2).sum(1))
rlim = np.linspace(min(r),max(r),1)

from mpl_toolkits import mplot3d
#定义一个绘制三维图像的函数
#elev表示上下旋转的角度
#azim表示平行旋转的角度
def plot_3D(elev=30,azim=30,X=X,y=y):
    ax = plt.subplot(projection="3d")
    ax.scatter3D(X[:,0],X[:,1],r,c=y,s=50,cmap='rainbow')
    ax.view_init(elev=elev, azim=azim)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("r")
    plt.show()


plot_3D()

# # 如果放到jupyter notebook中运行
# from sklearn.svm import SVC
# import matplotlib.pyplot as plt
# import numpy as np
# from sklearn.datasets import make_circles
#
# X, y = make_circles(100, factor=0.1, noise=.1)
# plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap="rainbow")
#
#
# def plot_svc_decision_function(model, ax=None):
#     if ax is None:
#         ax = plt.gca()
#     xlim = ax.get_xlim()
#     ylim = ax.get_ylim()
#
#     x = np.linspace(xlim[0], xlim[1], 30)
#     y = np.linspace(ylim[0], ylim[1], 30)
#     Y, X = np.meshgrid(y, x)
#     xy = np.vstack([X.ravel(), Y.ravel()]).T
#     P = model.decision_function(xy).reshape(X.shape)
#
#     ax.contour(X, Y, P, colors="k", levels=[-1, 0, 1], alpha=0.5, linestyles=["--", "-", "--"])
#     ax.set_xlim(xlim)
#     ax.set_ylim(ylim)
#
#
# clf = SVC(kernel="linear").fit(X, y)
# plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap="rainbow")
# plot_svc_decision_function(clf)
# r = np.exp(-(X ** 2).sum(1))
# rlim = np.linspace(min(r), max(r), 0.2)
# from mpl_toolkits import mplot3d
#
#
# def plot_3D(elev=30, azim=30, X=X, y=y):
#     ax = plt.subplot(projection="3d")
#     ax.scatter3D(X[:, 0], X[:, 1], r, c=y, s=50, cmap='rainbow')
#     ax.view_init(elev=elev, azim=azim)
#     ax.set_xlabel("x")
#     ax.set_ylabel("y")
#     ax.set_zlabel("r")
#     plt.show()
#
#
# from ipywidgets import interact, fixed
#
# interact(plot_3D,elev=[0,30],azip=(-180,180),X=fixed(X),y=fixed(y))
# plt.show()

"""
用一个平面将两类数据隔开，这个平面就是我们的超平面。
计算r，并将r作为数据的第三维度来讲数据升维的过程，被称为“核变换”，即是将数据投影到高维空间中，以寻找能够将数据完美分割的超平面，
而在高维空间中计算来找出超平面的函数就叫做核函数。
在SVM中，这个功能由参数“kernel”控制。
"""

clf = SVC(kernel = "rbf").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
plt.show()